package org.zjt.agent.runtime.reloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.RandomAccessFile;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.logging.Logger;

/**
 * Description:
 *
 * @author juntao.zhang
 * Date: 2018-10-24 3:32 PM
 * @see
 */
public class ClassFileReloader implements ClassReloader {

    private static final Logger LOGGER = Logger.getLogger(ClassFileReloader.class.getName());

    private final static String PATH_PATTERN = "/Users/juntao.zhang/dev/workspace/myproject/java-agent/java-agent-demo/src/main/resources/%s.class";

    private final Logger logger = Logger.getLogger(ClassFileReloader.class.getName());

    private Instrumentation inst;



    private List<String> classes = Arrays.asList("teController");

    public ClassFileReloader(Instrumentation inst) {
        this.inst = inst;
    }

    @Override
    public void startup() throws UnmodifiableClassException {
        Class[] allLoadedClasses = inst.getAllLoadedClasses();
        for (Class cl : allLoadedClasses){
            if (classes.contains(cl.getName())){
                inst.retransformClasses(cl);
            }
        }
    }


    private boolean match(String name){
        name = name.replace("/", ".");
        logger.info("match class "+name +" match "+ classes.contains(name));
        return classes.contains(name);
    }


    @Override
    public Optional<byte[]> redefineClass(String name,byte[] classfileBuffer){
        name = name.replace("/", ".");
        String fileName = name.contains(".") ? name.split("\\.") [name.split("\\.").length -1] : name;
        LOGGER.info("fileName:"+fileName);
        if (match(name)){
            return Optional.ofNullable(file2Byte(String.format(PATH_PATTERN,fileName)));
        }
        return Optional.empty();
    }


    public static byte[] file2Byte(String fileName) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        File file = new File(fileName);
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw")) {
            int read = randomAccessFile.getChannel().read(byteBuffer);
            while (read > 0){
                byteBuffer.flip();
                byte[] b = new byte[byteBuffer.remaining()];
                byteBuffer.get(b, 0, b.length);
                byteArrayOutputStream.write(b);
                byteBuffer.rewind();
                read = randomAccessFile.getChannel().read(byteBuffer);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return byteArrayOutputStream.toByteArray();
    }

}
